/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<fs.h>

struct file file[NFILE];
struct inode inode[NINODE];

int 
bbread(uint bn, void *buf)
{
	struct buf *bp;

	bp = bread(rootdev, bn);
	if(bp->b_flags & B_ERROR){
		brelse(bp);
		return -1;
	}
	memmove(buf, bp->b_addr, BLKSIZ);
	brelse(bp);
	return 0;
}

int
bbwrite(uint bn, void *buf)
{
	struct buf *bp;

	bp = getblk(rootdev, bn);
	memmove(bp->b_addr, buf, BLKSIZ);	
	bwrite(bp);
	if(bp->b_flags & B_ERROR)
		return -1;
	return 0;
}

struct inode *
iget(dev_t dev, ino_t ino)
{
	struct inode *p, *ip;

loop:
	ip = NULL;
	for(p = &inode[0]; p < &inode[NINODE]; p++){
		if(p->i_dev == dev && p->i_number == ino){
			if(p->i_flag & ILOCK){
				p->i_flag |= IWANT;
				sleep(p, PINOD);
				goto loop;
			}
			p->i_count++;
			p->i_flag |= ILOCK;
			return p;
		}
		if(ip == NULL && p->i_count == 0)
			ip = p;
	}
	if((p = ip) == NULL){
		up->p_error = ENFILE;	
		return NULL;
	}
	if(iread(dev, ino, p) == -1){
		up->p_error = EIO;
		return NULL;
	}
	p->i_count = 1;
	p->i_dev = dev;
	p->i_number = ino;
	p->i_flag = ILOCK;
	return p;
}

void
iput(struct inode *ip)
{
	if(ip == NULL)
		return;
	ip->i_flag |= ILOCK;
	if(--ip->i_count == 0){
		if(ip->i_nlink <= 0){
			itrunc(ip);
			ip->i_mode = 0;
			ifree(ip->i_dev, ip->i_number);
		}
		iupdat(ip);
		ip->i_dev = ip->i_flag = ip->i_number = 0;
	}
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag & IWANT){
		ip->i_flag &= ~IWANT;
		wakeup(ip);
	}
}

int
iupdat(struct inode *ip)
{
	if(ip == NULL)
		return 0;
	if(ip->i_flag & (IACC | IUPD | ICHG)){
		if(ip->i_flag & IACC)
			ip->i_atime = time(NULL);
		if(ip->i_flag & IUPD)	
			ip->i_mtime = time(NULL);
		if(ip->i_flag & ICHG)	
			ip->i_ctime = time(NULL);
		ip->i_flag &= ~(IUPD | IACC | ICHG);
		return iwrite(ip);
	}
	return 0;
}

int
itrunc(struct inode *ip)
{
	dev_t dev;
	uint buf[256];
	uint buf1[256];
	uint buf2[256];
	uint i, j, k, n, *p;

	if(ip == NULL)
		return -1;
	if(ip->i_mode & (S_IFCHR | S_IFBLK))
		return 0;
	p = ip->i_addr;
	dev = ip->i_dev;
	for(i = 0; i < NADDR; i++){
	    	if(p[i]){
			if(i >= 10){
		    		if(bbread(p[i], buf) == -1){
					up->p_error = EIO;
					return -1;
		    		}
		     		for(j = 0; j < 256; j++){
					if(buf[j]){
		           			if(i >= 11){
							if(bbread(buf[j], buf1) == -1){
				    				up->p_error = EIO;
				    				return -1;
							}
							for(k = 0; k < 256; k++){
								if(buf1[k]){
									if(i >= 12){
										if(bbread(buf1[k], buf2) == -1){
											up->p_error = EIO;
											return -1;
										}
										for(n = 0; n < 256; n++){
											if(buf2[n])
												free(dev, buf2[n]);
										}
									}
									free(dev, buf1[k]);
								}
							}
						}
						free(dev, buf[j]);
					}
			}
		}
		free(dev, p[i]);
		p[i] = 0;
	   }
	}
	ip->i_size = 0;
	ip->i_flag |= IUPD;
	ip->i_flag |= ICHG;
	return 0;
}

int
iread(dev_t dev, ino_t ino, struct inode *ip)
{
	struct buf *bp;
	struct filsys *sp;
	
	sp = &mount[MINOR(dev)].m_spb;
	bp = bread(dev, NRSVSCT + SPBSIZ + sp->s_ssize + (ino - 1) / NIOPB);
        if(bp->b_flags & B_ERROR){
		brelse(bp);
                return -1;
	}
	memmove(ip, (char *)bp->b_addr + ((ino - 1) % NIOPB) * sizeof(struct dinode), sizeof(struct dinode));
	brelse(bp);
	return 0;
}

int
iwrite(struct inode *ip)
{
	dev_t dev;
	ino_t ino;
	struct buf *bp;
	struct filsys *sp;

	dev = ip->i_dev;
	ino = ip->i_number;
	sp = &mount[MINOR(ip->i_dev)].m_spb;
	bp = bread(dev, NRSVSCT + SPBSIZ + sp->s_ssize + (ino - 1) / NIOPB);
	if(bp->b_flags & B_ERROR){
		brelse(bp);
		return -1;
	}
	memmove((char *)bp->b_addr + ((ino - 1) % NIOPB) * sizeof(struct dinode), ip, sizeof(struct dinode));
	bwrite(bp);
	if(bp->b_flags & B_ERROR)
		return -1;
	return 0;
}

int
log_read(uint bn, void *buf, struct inode *ip, uint cnt)
{
	uint i, nb;

	for(i = 0; i < cnt; i++){
		if(nb = bmap(ip, bn + i)){
			if(bbread(nb, buf + i * BLKSIZ))
				break;
		}else
			break;
	}
	return i;
}

int 
log_write(uint bn, void *buf, struct inode *ip, uint cnt)
{
	uint i, nb;
	
	for(i = 0; i < cnt; i++){
		if(nb = bmap(ip, bn + i)){
			if(bbwrite(nb, buf + i * BLKSIZ))
				break;
		}else 
			break;
	}
	return i;
}

uint
bmap(struct inode *ip, uint bn)
{
	dev_t dev;
        uint i, j, n, nb;
        uint buf[256], buf1[256], buf2[256];

	dev = ip->i_dev;
        if(bn < NADDR - 3){
                if(!ip->i_addr[bn])
                        ip->i_addr[bn] = alloc(dev);
                return ip->i_addr[bn];
        }
        nb = 1;
        bn -= NADDR - 3;
        for(i = 3; i > 0; i--){ 
                nb <<= 8;
                if(bn < nb)
                        break;
                bn -= nb;
        }
        if(!i)
                return 0;
        j = NADDR - i;
        if(!ip->i_addr[j] && !(ip->i_addr[j] = balloc(dev)))
                return 0;
        nb = ip->i_addr[j];
        if(bbread(nb, buf))
                return 0;
        switch(i){      
        case 1:
                memmove(buf2, buf, BLKSIZ);
                if(!(n = buf2[bn >> 16]) && !(buf2[bn >> 16] = balloc(dev)))
                        return 0;
                if(!n && bbwrite(nb, buf2)){
			free(dev, buf2[bn >> 16]);
                        return 0;
		}
                nb = buf2[bn >> 16];
                if(bbread(nb, buf1))
                        return 0;
                bn &= 0xffff;
        case 2:
                if(i == 2)
                        memmove(buf1, buf, BLKSIZ);
                if(!(n = buf1[bn >> 8]) && !(buf1[bn >> 8] = balloc(dev)))
                        return 0;
                if(!n && bbwrite(nb, buf1)){
			free(dev, buf1[bn >> 8]);
                        return 0;
		}
                nb = buf1[bn >> 8];
                bn &= 0xff;
        case 3:
                if(i != 3 && bbread(nb, buf))
                        return 0;
		if(!(n = buf[bn]))
			buf[bn] = alloc(dev);
                if(!n && bbwrite(nb, buf)){
			free(dev, buf[bn]);
                        return 0;
		}
                return buf[bn];
        }
        return 0;
}

ino_t
findfile(struct inode *ip, char *name)
{
	int i, j, k;
	struct direct dbuf[NDIRENT];

	for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
		if(log_read(i, dbuf, ip, 1) != 1)
			return 0;
		for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++)
			if(dbuf[k].d_ino && strcmp(dbuf[k].d_name, name) == 0)
				return dbuf[k].d_ino;
	}
	return 0;
}

char *
have_path(char *path, char *buf)
{
        char *s, *p;

	buf[0] = '\0';
        for(p = path; *p && *p == '/'; p++)
                ;
        if(*p){
                for(s = p; *p && *p != '/'; p++)
                        ;
                memmove(buf, s, p - s >= DIRSIZ ? DIRSIZ : p - s);
		buf[p - s < DIRSIZ ? p - s : DIRSIZ - 1] = '\0';
                while(*p && *p == '/')
                        p++;
        }
	return *p ? p : NULL;
}

struct inode *
namei(char *pathname, char flag, ino_t ino1)
{
	ino_t ino;
	struct direct dir;
	char *p, buf[DIRSIZ];	
	struct inode *dp, *ip;

	p = pathname;	
	dp = up->p_cdir;
	if(*p == '/')
		dp = rootp;
	dp = iget(rootdev, dp->i_number); 
	while(p = have_path(p, buf)){
		if(!S_ISDIR(dp->i_mode) || access0(dp, S_IXUSR))
			goto out;
		if(!(ino = findfile(dp, buf)))
			goto out;
		if(!(ip = iget(rootdev, ino)))
			goto out;
		iput(dp);
		dp = ip;
	}
	if(!buf[0])
		return dp;
	if(!S_ISDIR(dp->i_mode) || access0(dp, S_IXUSR))
		goto out;
	switch(flag){
	case 0: 
		ino = findfile(dp, buf);
		iput(dp);
		return ino ? iget(rootdev, ino) : NULL;
	case 1: 
		if(ino = findfile(dp, buf))
			goto out;
		if(access0(dp, S_IWUSR))
			goto out;
		dir.d_ino = ino1;
		if(!dir.d_ino && !(dir.d_ino = ialloc(rootdev)))
			goto out;
		memmove(dir.d_name, buf, DIRSIZ);
		if(wddir(dp, &dir, 0))
			goto out;
		iput(dp);
		return iget(rootdev, dir.d_ino);
	case 2:
		if(!(ino = findfile(dp, buf)))
			goto out;
		if(!(ip = iget(rootdev, ino)))
			goto out;
		dir.d_ino = ino;
		memmove(dir.d_name, buf, DIRSIZ);
		if(wddir(dp, &dir, 1)){
			iput(ip);
			goto out;
		}
		if(S_ISDIR(ip->i_mode)){
			if(ip->i_nlink == 2){
				dp->i_nlink--;
				ip->i_nlink = 0;
			}else
				goto out;
		}else
			ip->i_nlink--;
		ip->i_flag |= ICHG;
		iput(ip);
		iput(dp);
		return dp;
	case 3:
		return dp;	
	}
out:
	iput(dp);
	return NULL;
}

int
wddir(struct inode *ip, struct direct *dp, int flag)
{
	uint i, j, k;
	struct direct dbuf[NDIRENT];

	if(flag){	/* delete */
		for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
			if(log_read(i, dbuf, ip, 1) != 1)
				return -1;
			for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
				if(strcmp(dbuf[k].d_name, dp->d_name) == 0){
					dbuf[k].d_ino = 0; 		
					return log_write(i, dbuf, ip, 1) ? 0 : -1;
				}
			}
		}
		return 0;
	}else{	/* add */
		for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
			if(log_read(i, dbuf, ip, 1) != 1)
				break;
			for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
				if(dbuf[k].d_ino == 0){
					dbuf[k].d_ino = dp->d_ino;
					memmove(dbuf[k].d_name, dp->d_name, DIRSIZ);
					return log_write(i, dbuf, ip, 1) ? 0 : -1;
				}
			}
		}
		return ioadd(ip, dp, sizeof(struct direct)) == sizeof(struct direct) ? 0 : -1;
	}
}

uint
ioadd(struct inode *ip, void *buf, uint cnt)
{
	uint off, rtv;
	uint i, n, bn;
	char tmp[BLKSIZ];

	if(cnt == 0)
		return 0;	
	i = 0;
	off = ip->i_size;
	ip->i_size += cnt;	
	ip->i_flag |= ICHG;
	bn = (off & BLK_MASK) >> BLK_OFF;
	if(off % BLKSIZ){
		if(log_read(bn, tmp, ip, 1) != 1){
			rtv = 0;
			goto out;
		}
		if((i = BLKSIZ - off % BLKSIZ) > cnt)
			i = cnt;
		memmove(tmp + off % BLKSIZ, buf, i);
		if(log_write(bn, tmp, ip, 1) != 1){
			rtv = 0;
			goto out;
		}
		bn++;
		off += i;
	}
	if((cnt - i) / BLKSIZ){
		n = log_write(bn, buf + i, ip, (cnt - i) / BLKSIZ);
		off += n * BLKSIZ;
		if(n < (cnt - i) / BLKSIZ){
			rtv = i + n * BLKSIZ;
			goto out;
		}
		bn += n;
		i += n * BLKSIZ;
	}
	if(cnt - i){
		if(log_read(bn, tmp, ip, 1) != 1){
			rtv = i;	
			goto out;
		}
		memmove(tmp, buf + i, cnt - i);
		if(log_write(bn, tmp, ip, 1) != 1){
			rtv = i;
			goto out;
		}
	}
	rtv = cnt;
out:
	if(rtv > 0)
		ip->i_flag |= IUPD;
	iupdat(ip);
	return rtv;
}

int
access0(struct inode *ip, mode_t mode)
{
	if(up->p_uid == 0){
		if(mode == S_IXUSR && !(ip->i_mode | (S_IXOTH | S_IXUSR | S_IXGRP)))
			goto out;
		if(mode == S_IRUSR && !(ip->i_mode | (S_IROTH | S_IRUSR | S_IRGRP)))
			goto out;
		if(mode == S_IWUSR && !(ip->i_mode | (S_IWOTH | S_IWUSR | S_IWGRP)))
			goto out;
		return 0;
	}
	if(up->p_uid != ip->i_uid){
		mode >>= 3;		
		if(up->p_gid != ip->i_gid)
			mode >>= 3;
	}
	if(ip->i_mode & mode)
		return 0;
out:
	up->p_error = EACCES;
	return -1;
}

int
isempty(struct inode *ip)
{
	uint i, j, k, cnt;
        struct direct dbuf[NDIRENT];

        cnt = 0;
        for(i = 0, j = ip->i_size / BLKSIZ; i <= j; i++){
                if(log_read(i, dbuf, ip, 1) != 1)
                        return -1;
                for(k = 0; k < (i != j ? NDIRENT : (ip->i_size % BLKSIZ) / sizeof(struct direct)); k++){
                        if(dbuf[k].d_ino && ++cnt > 2)
                                return 0;
                }
        }
        return cnt == 2 ? 1 : 0;
} 
